/**
 * Copyright (C) 2008 by The Regents of the University of California
 * Redistribution of this file is permitted under the terms of the GNU
 * Public License (GPL).
 *
 * @author Junghoo "John" Cho <cho AT cs.ucla.edu>
 * @date 3/24/2008
 */

#include <cstdio>
#include <iostream>
#include <fstream>
#include "Bruinbase.h"
#include "SqlEngine.h"
#include "RecordFile.h"
#include "BTreeIndex.h"
#include "BTreeNode.h"
#include <vector>


using namespace std;

// external functions and variables for load file and sql command parsing 
extern FILE* sqlin;
int sqlparse(void);


RC SqlEngine::run(FILE* commandline)
{
  fprintf(stdout, "Bruinbase> ");

  // set the command line input and start parsing user input
  sqlin = commandline;
  sqlparse();  // sqlparse() is defined in SqlParser.tab.c generated from
               // SqlParser.y by bison (bison is GNU equivalent of yacc)

  return 0;
}

RC SqlEngine::select(int attr, const string& table, const vector<SelCond>& cond)
{
  RecordFile rf;   // RecordFile containing the table
  RecordId   rid;  // record cursor for table scanning

  RC     rc;
  int    key;     
  string value;
  int    count;
  int    diff;

  // open the table file
  if ((rc = rf.open(table + ".tbl", 'r')) < 0) {
    fprintf(stderr, "Error: table %s does not exist\n", table.c_str());
    return rc;
  }

/*************
 * Use index *
**************/
	BTreeIndex index;
	vector<RecordId> result;
	int keyCount;

	// For every condition in where clause
	for ( int i = 0; i < cond.size(); i ++)
	{
		// Switch on the comparators, we only use index on 
		// EQ, LT, GT, LE, GE. 
		// Don't use index on NE
		if ( cond[i].comp == SelCond::NE)
			goto full_scan;
		else
		{
			index.open(table, 'r');
			IndexCursor cursor;
			keyCount = index.getLeafKeyCount(0);
			int status;
			int searchKey = atoi(cond[i].value);
			int diff;

			switch(cond[i].comp)
			{
				case SelCond::EQ:
				{
					status = index.locate(searchKey, cursor);
					if ( status == 0)
						index.readForward(cursor, key, rid);
					else
					{
						fprintf(stderr, "Error: Cannot find the tuple in table: %s\n", table.c_str());
						exit(1);
					}		
				
					// If the condition is not met, check the next condition
					diff = key - atoi(cond[i].value);
					if ( diff == 0)
						result.push_back(rid);
					break;
				}
				case SelCond::LT:
				case SelCond::LE:
				{
					// Locate the searchKey 
					status = index.locate(searchKey, cursor);
					if ( status == 0)
					{
						// Search from the very left to searchKey
						for ( int j = 0; j < cursor.eid; j ++)
						{
							IndexCursor pos;
							pos.pid = 0;
							pos.eid = j;

							index.readForward(pos, key, rid);
							diff = key - atoi(cond[i].value);

							if ( cond[i].comp == SelCond::LT)
							{	
								if ( diff < 0 )
									result.push_back(rid);
							}	
							else
							{
								if ( diff <= 0)
									result.push_back(rid);
							}
						}
					}
					break;
				}
				case SelCond::GT:
				case SelCond::GE:
				{
					// Search from the searchKey and keep everything to the right
					status = index.locate(searchKey, cursor);
					if ( status == 0)
					{
						for ( int j = cursor.eid; j < keyCount; j++)
						{
							cursor.pid = 0;
							cursor.eid = j;
			
							index.readForward(cursor, key, rid);
							diff = key - atoi(cond[i].value);

							if ( cond[i].comp == SelCond::GT)
							{
								if ( diff > 0 )
									result.push_back(rid);
							}
							else
							{
								if ( diff >= 0 )
									result.push_back(rid);
							}

						}
					}
					else
					{
						fprintf(stderr, "Error: Cannot find the tuple in table: %s\n", table.c_str());
						exit(1);
					}
					break;
				}

			}
		}
		index.close();
	}
	
	// If there's no condition,
	// then return everything
	if ( cond.size() == 0)
	{
		IndexCursor cursor;

		index.open(table, 'r');
		keyCount = index.getLeafKeyCount(0);

		for ( int i = 0; i < keyCount; i++)
		{
			cursor.pid = 0;
			cursor.eid = i;
	
			index.readForward(cursor, key, rid);
			result.push_back(rid);
		}

	}

	// Handle count(*)
	if ( attr == 4)
	{
		vector<RecordId> temp;
		while ( !result.empty())
		{
			rf.read(result.back(), key, value);

			if ( key != 0 && value != "")
				temp.push_back(result.back());
			result.pop_back();
		}

		fprintf(stdout, "%d\n", temp.size());
		
	}
	// Finally, do a switch on attr to determine what attr to output
	else
	{
		while ( !result.empty())
		{
			rf.read(result.back(), key, value);

			if ( key == 0 || value == "")
			{
				result.pop_back();
				continue;
			}

			switch(attr)
			{	
				case 1:	fprintf(stdout, "%d\n", key);				break;
				case 2:	fprintf(stdout, "%s\n", value.c_str());			break;
				case 3:	fprintf(stdout, "%d '%s'\n", key, value.c_str());	break;
			}
			result.pop_back();
		}
	}

	// Close the index and the record files
	rf.close();
	return 0;

/************************
 * Do a full table scan *
 * **********************/

full_scan:
  // scan the table file from the beginning
  rid.pid = rid.sid = 0;
  count = 0;
  while (rid < rf.endRid()) {
    // read the tuple
    if ((rc = rf.read(rid, key, value)) < 0) {
      fprintf(stderr, "Error: while reading a tuple from table %s\n", table.c_str());
      goto exit_select;
    }

    // check the conditions on the tuple
    for (unsigned i = 0; i < cond.size(); i++) {
      // compute the difference between the tuple value and the condition value
      switch (cond[i].attr) {
      case 1:
	diff = key - atoi(cond[i].value);
	break;
      case 2:
	diff = strcmp(value.c_str(), cond[i].value);
	break;
      }

      // skip the tuple if any condition is not met
      switch (cond[i].comp) {
      case SelCond::EQ:
	if (diff != 0) goto next_tuple;
	break;
      case SelCond::NE:
	if (diff == 0) goto next_tuple;
	break;
      case SelCond::GT:
	if (diff <= 0) goto next_tuple;
	break;
      case SelCond::LT:
	if (diff >= 0) goto next_tuple;
	break;
      case SelCond::GE:
	if (diff < 0) goto next_tuple;
	break;
      case SelCond::LE:
	if (diff > 0) goto next_tuple;
	break;
      }
    }

    // the condition is met for the tuple. 
    // increase matching tuple counter
    count++;

    // print the tuple 
    switch (attr) {
    case 1:  // SELECT key
      fprintf(stdout, "%d\n", key);
      break;
    case 2:  // SELECT value
      fprintf(stdout, "%s\n", value.c_str());
      break;
    case 3:  // SELECT *
      fprintf(stdout, "%d '%s'\n", key, value.c_str());
      break;
    }

    // move to the next tuple
    next_tuple:
    ++rid;
  }

  // print matching tuple count if "select count(*)"
  if (attr == 4) {
    fprintf(stdout, "%d\n", count);
  }
  rc = 0;

  // close the table file and return
  exit_select:
  rf.close();
  return rc;
}

RC SqlEngine::load(const string& table, const string& loadfile, bool index)
{
	// Open the loadfile with fstream	
	fstream stream;
	stream.open(loadfile.c_str());
	if (!stream)
		cout << "Error opening file" << endl;

	// Create or open a load file in write mode
	RecordFile *rf = NULL;
	rf = new RecordFile();

	const string tableName = table + ".tbl";

	if (rf->open(tableName, 'w') < 0)
		cout << "Error opening file in record file" << endl;

	// while not EOF
	char ch;
	BTreeIndex idx;
	if (index)
		idx.open(table, 'w');

	while( stream.get(ch))
	{
		string line, value;
		int key;
		RecordId rid;	

		// Parse the loadfile to get each individual line
		while (ch != '\n')
		{
			line += ch;
			stream.get(ch);
		}
		
		// Pass the line to parseLoadLine function with key and value
		// and pass the key and value to append.
		parseLoadLine(line, key, value);

		const string value2 = value;		
		rid = rf->endRid();	
		rf->append(key, value2, rid);

		// rid is returned as the newly inserted tuple in RecordFile
		// call BTreeIndex::insert() with key and rid to create an index in the B+ tree
		if (index)
			idx.insert(key, rid);

	}
	if (index)
		idx.close();

	// Close the 'table' file 
	if (rf->close() < 0)
		cout << "Error closing the file" << endl;

	delete rf;	
  	return 0;
}

RC SqlEngine::parseLoadLine( const string& line, int& key, string& value)
{
    const char *s;
    char        c;
    string::size_type loc;
    
    // ignore beginning white spaces
    c = *(s = line.c_str());
    while (c == ' ' || c == '\t') { c = *++s; }

    // get the integer key value
    key = atoi(s);

    // look for comma
    s = strchr(s, ',');
    if (s == NULL) { return RC_INVALID_FILE_FORMAT; }

    // ignore white spaces
    do { c = *++s; } while (c == ' ' || c == '\t');
    
    // if there is nothing left, set the value to empty string
    if (c == 0) { 
        value.erase();
        return 0;
    }

    // is the value field delimited by ' or "?
    if (c == '\'' || c == '"') {
        s++;
    } else {
        c = '\n';
    }

    // get the value string
    value.assign(s);
    loc = value.find(c, 0);
    if (loc != string::npos) { value.erase(loc); }

    return 0;
}
